﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;

namespace gt.framework.Utils
{
    public class FTPUtils
    {
        private string _userName;
        private string _password;
        private string _domain;

        public FTPUtils(string userName, string password)
            : this(userName, password, string.Empty)
        {
        }

        public FTPUtils(string userName, string password, string domain)
        {
            this._password = password;
            this._userName = userName;
            this._domain = domain;
        }

        private FtpWebRequest Create(string ftpFullPath, string ftpMethod)
        {
            // 根据uri创建FtpWebRequest对象 
            FtpWebRequest ftpRequest = (FtpWebRequest)FtpWebRequest.Create(new Uri(ftpFullPath));

            // 指定数据传输类型
            ftpRequest.UseBinary = true;
            //代理
            ftpRequest.Proxy = null;

            // ftp用户名和密码
            ftpRequest.Credentials = new NetworkCredential(this._userName, this._password, this._domain);

            // 默认为true，连接不会被关闭
            // 在一个命令之后被执行
            ftpRequest.KeepAlive = false;

            // 指定执行什么命令
            ftpRequest.Method = ftpMethod;

            return ftpRequest;
        }

        private void Upload(Stream fileStream, string ftpFullPath, string ftpMethod)
        {
            FtpWebRequest ftpRequest = this.Create(ftpFullPath, ftpMethod);
            ftpRequest.ContentLength = fileStream.Length;

            // 缓冲大小设置为2kb
            int buffLength = 2048;

            byte[] buff = new byte[buffLength];
            int contentLen;
            using (Stream strm = ftpRequest.GetRequestStream())
            {
                // 每次读文件流的2kb
                contentLen = fileStream.Read(buff, 0, buffLength);

                // 流内容没有结束
                while (contentLen != 0)
                {
                    // 把内容从file stream 写入 upload stream
                    strm.Write(buff, 0, contentLen);
                    contentLen = fileStream.Read(buff, 0, buffLength);
                }

                strm.Close();
            }
        }

        /// <summary>
        /// Upload file to FTP server
        /// </summary>
        /// <param name="fileStream"></param>
        /// <param name="ftpFullPath"></param>
        public void Upload(Stream fileStream, string ftpFullPath)
        {
            this.Upload(fileStream, ftpFullPath, WebRequestMethods.Ftp.UploadFile);
        }

        /// <summary>
        /// Upload append file to FTP server
        /// </summary>
        /// <param name="fileStream"></param>
        /// <param name="ftpFullPath"></param>
        public void UploadAppendFile(Stream fileStream, string ftpFullPath)
        {
            this.Upload(fileStream, ftpFullPath, WebRequestMethods.Ftp.AppendFile);
        }

        /// <summary>
        /// Download file
        /// </summary>
        /// <param name="ftpFullPath"></param>
        /// <returns></returns>
        public Stream Download(string ftpFullPath)
        {
            FtpWebRequest ftpRequest = this.Create(ftpFullPath, WebRequestMethods.Ftp.DownloadFile);

            using (FtpWebResponse response = (FtpWebResponse)ftpRequest.GetResponse())
            {
                return response.GetResponseStream();
            }
        }

        /// <summary>
        /// Get file list
        /// </summary>
        /// <param name="ftpDirectory"></param>
        /// <returns></returns>
        public string[] GetFileList(string ftpDirectory)
        {
            if (!ftpDirectory.EndsWith("/")) ftpDirectory += "/";
            StringBuilder result = new StringBuilder();
            FtpWebRequest ftpRequest = this.Create(ftpDirectory, WebRequestMethods.Ftp.ListDirectory);
            using (FtpWebResponse response = (FtpWebResponse)ftpRequest.GetResponse())
            {
                using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                {
                    string line = reader.ReadLine();
                    while (line != null)
                    {
                        result.Append(line);
                        result.Append("\n");
                        line = reader.ReadLine();
                    }
                    // to remove the trailing '\n'        
                    if (result.Length > 0) result.Remove(result.ToString().LastIndexOf('\n'), 1);
                    reader.Close();
                }
                response.Close();
            }
            return result.ToString().Split('\n');
        }

        private void Action(string ftpFullPath, string ftpMethod)
        {
            FtpWebRequest ftpRequest = this.Create(ftpFullPath, ftpMethod);
            using (FtpWebResponse response = (FtpWebResponse)ftpRequest.GetResponse())
            {
                response.Close();
            }
        }

        /// <summary>
        /// Create directory
        /// </summary>
        /// <param name="ftpFullPath"></param>
        public void CreateDirectory(string ftpDirectoryPath)
        {
            string[] pathArr = ftpDirectoryPath.Replace("//", "/").Trim('/').Split('/');
            StringBuilder basePath = new StringBuilder(string.Format("{0}//{1}/", pathArr[0], pathArr[1]));
            for (int i = 2; i < pathArr.Length; i++)
            {
                List<string> fileDirectories = GetFileList(basePath.ToString()).ToList();
                if (!fileDirectories.Exists((e) => e.Equals(pathArr[i], StringComparison.InvariantCultureIgnoreCase)))
                {
                    for (; i < pathArr.Length; i++)
                    {
                        basePath.Append(pathArr[i]);
                        basePath.Append("/");
                        this.Action(basePath.ToString(), WebRequestMethods.Ftp.MakeDirectory);
                    }
                }
            }
        }

        /// <summary>
        /// Delete directory
        /// </summary>
        /// <param name="ftpFullPath"></param>
        public void DeleteDirectory(string ftpDirectoryPath)
        {
            if (!ftpDirectoryPath.EndsWith("/")) ftpDirectoryPath += "/";

            this.Action(ftpDirectoryPath, WebRequestMethods.Ftp.RemoveDirectory);
        }

        /// <summary>
        /// Delete file
        /// </summary>
        /// <param name="ftpFullPath"></param>
        public void DeleteFile(string ftpFullPath)
        {
            this.Action(ftpFullPath, WebRequestMethods.Ftp.DeleteFile);
        }

        /// <summary>
        /// Check directory path exists
        /// </summary>
        /// <param name="ftpDirectoryPath"></param>
        /// <returns></returns>
        public bool CheckDirectoryExists(string ftpDirectoryPath)
        {
            if (string.IsNullOrEmpty(ftpDirectoryPath)) return false;

            string[] pathArr = ftpDirectoryPath.Replace("//", "/").Trim('/').Split('/');
            string basePath = string.Format("{0}//{1}/", pathArr[0], pathArr[1]);
            if (pathArr.Length < 3) return false;
            for (int i = 2; i < pathArr.Length; i++)
            {
                List<string> fileDirectories = GetFileList(basePath).ToList();
                if (!fileDirectories.Exists((e) => e.Equals(pathArr[i], StringComparison.InvariantCultureIgnoreCase)))
                    return false;
                basePath += pathArr[i] + "/";
            }

            return true;
        }

        /// <summary>
        /// Check file exists
        /// </summary>
        /// <param name="ftpFullPath"></param>
        /// <returns></returns>
        public bool CheckFileExists(string ftpFullPath)
        {
            string ftpDirectoryPath = PathUtils.GetDirectoryName(ftpFullPath);
            if (!this.CheckDirectoryExists(ftpDirectoryPath)) return false;

            FtpWebRequest ftpRequest = this.Create(ftpFullPath, WebRequestMethods.Ftp.ListDirectoryDetails);
            using (FtpWebResponse response = (FtpWebResponse)ftpRequest.GetResponse())
            {
                using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                {
                    string line = reader.ReadLine();
                    if (string.IsNullOrEmpty(line)) return false;
                }
            }
            return true;
        }
    }
}
